#include "Encoder.hpp"
#include <chrono>
#include <cstring>
#include <gst/gstbuffer.h>
#include <gst/gstclock.h>
#include <gst/gstcompat.h>
#include <gst/gstmemory.h>
#include <gst/gstutils.h>
#include <regex>
#include <spdlog/spdlog.h>
#include <stdexcept>

void enough_data_callback(GstAppSrc *self, gpointer user_data) {
  Encoder *coder = (Encoder *)user_data;
  coder->enoughDataFlag = true;
}

void need_data_callback(GstAppSrc *self, guint length, gpointer user_data) {
  Encoder *coder = (Encoder *)user_data;
  coder->enoughDataFlag = false;
}

Encoder::Encoder(const std::string &srturi) : BaseEncoder(srturi) {
  std::smatch streamID, url;
  if (std::regex_search(srturi, streamID, std::regex(R"(#!::.*)")) != 1)
    throw std::runtime_error("EncoderErr: Can't find streamid");
  if (std::regex_search(srturi, url,
                        std::regex(R"(srt://(\w+\.)?+\w+(\:[0-9]+)?)")) != 1)
    throw std::runtime_error("EncoderErr: Can't find srt url");

  gst_init(nullptr, nullptr);

#define MAKE_ELEMENT(name)                                                     \
  name = gst_element_factory_make(#name, #name);                               \
  if (!name)                                                                   \
    std::runtime_error("EncoderErr: Can't make element " #name);

  MAKE_ELEMENT(appsrc)
  MAKE_ELEMENT(videoconvert)
  MAKE_ELEMENT(vah264enc)
  MAKE_ELEMENT(mpegtsmux)
  MAKE_ELEMENT(srtsink)

#undef MAKE_ELEMENT

  g_object_set(srtsink, "uri", url[0].str().c_str(), nullptr);
  g_object_set(srtsink, "streamid", streamID[0].str().c_str(), nullptr);
  g_object_set(appsrc, "max-buffers", 10, nullptr);
  g_object_set(appsrc, "format", GST_FORMAT_TIME, nullptr);

  g_signal_connect(appsrc, "enough-data", G_CALLBACK(enough_data_callback),
                   this);
  g_signal_connect(appsrc, "need-data", G_CALLBACK(need_data_callback), this);

  if (pipeline = gst_pipeline_new("workpipe"), !pipeline)
    throw std::runtime_error("EncoderErr: Can't make pipeline");

  gst_bin_add_many(GST_BIN(pipeline), appsrc, videoconvert, vah264enc,
                   mpegtsmux, srtsink, nullptr);

#define LINK_ELEMENT(A, B)                                                     \
  if (!gst_element_link(A, B))                                                 \
    throw std::runtime_error("EncoderErr: Can't link" #A " and " #B);

  LINK_ELEMENT(videoconvert, vah264enc)
  LINK_ELEMENT(vah264enc, mpegtsmux)
  LINK_ELEMENT(mpegtsmux, srtsink)

#undef LINK_ELEMENT
}

Encoder::~Encoder() {
  if (status)
    EndStream();
  gst_object_unref(pipeline);
}

void Encoder::StartStream(int width, int height, const ImageType &type) {
  GstCaps *srccap = gst_caps_new_simple(
      "video/x-raw", "format", G_TYPE_STRING,
      [&type, &width, &height, this]() -> std::string {
        switch (type) {
        case ImageType::GREY:
          imageSize = width * height;
          return "GRAY8";
          break;
        case ImageType::RGB8:
          imageSize = width * height * 3;
          return "RGB";
          break;
        }
      }()
                                              .c_str(),
      "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, "framerate",
      GST_TYPE_FRACTION, 30, 1, nullptr);
  gst_app_src_set_caps(GST_APP_SRC_CAST(appsrc), srccap);
  gst_caps_unref(srccap);

#define LINK_ELEMENT(A, B)                                                     \
  if (!gst_element_link(A, B))                                                 \
    throw std::runtime_error("EncoderErr: Can't link" #A " and " #B);

  LINK_ELEMENT(appsrc, videoconvert);

#undef LINK_ELEMENT

  if (gst_element_set_state(pipeline, GST_STATE_PLAYING) ==
      GST_STATE_CHANGE_FAILURE)
    throw std::runtime_error(
        "EncoderErr: Gstreamer pipeline change status failed");
  startTime = std::chrono::steady_clock::now();
  status = true;
}

void MemoryDestroy(gpointer data) {
  if (data)
    delete[] (unsigned char *)data;
}

void Encoder::InputImage(const unsigned char *img) {
  if (enoughDataFlag)
    return;
  gpointer data;
  data = g_malloc(imageSize);
  memcpy(data, img, imageSize);
  GstBuffer *buffer = gst_buffer_new_wrapped(data, imageSize);
  GST_BUFFER_DTS(buffer) = GST_CLOCK_TIME_NONE;
  // std::chrono::duration_cast<std::chrono::duration<long>>(
  //     std::chrono::steady_clock::now() - startTime)
  //     .count();
  gst_app_src_push_buffer(GST_APP_SRC_CAST(appsrc), buffer);
}

void Encoder::EndStream() {
  gst_app_src_end_of_stream(GST_APP_SRC_CAST(appsrc));
  gst_element_set_state(pipeline, GST_STATE_NULL);

  gst_element_unlink(appsrc, videoconvert);

  status = false;
}

bool Encoder::GetStatus() { return status; }
